package com.googlecode.cswish.util;

import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.log4j.Logger;
import org.apache.struts2.StrutsStatics;
import org.hibernate.LazyInitializationException;
import org.hibernate.proxy.HibernateProxy;

import com.googlecode.cswish.annotation.GenericType;
import com.googlecode.cswish.annotation.Protected;
import com.googlecode.cswish.exception.ApplicationException;
import com.googlecode.cswish.model.ModelInfo;
import com.googlecode.cswish.model.ParameterMap;
import com.googlecode.cswish.security.SafeManager;
import com.googlecode.cswish.security.auth.UserInfo;
import com.googlecode.cswish.struts.Invoker;
import com.googlecode.cswish.struts.Routing;
import com.googlecode.cswish.struts.hibernate.DynamicClassLoaderFactory;
import com.googlecode.cswish.struts.hibernate.HibernateUtil;
import com.googlecode.cswish.struts.hibernate.IDynamicModel;
import com.googlecode.cswish.struts.spring.BeanIntrospector;
import com.googlecode.cswish.struts.spring.ObjectFactory;
import com.opensymphony.xwork2.ActionContext;

/**
 * Provide some simple functions to help get the class information
 * 
 * @author feng Date: 2009-5-24
 *
 */
public class ClassUtil {

	private final static Logger logger = Logger.getLogger(ClassUtil.class);
	
	private final static Map<String, Class> privitiveClass = new HashMap<String, Class>();
	static {
		privitiveClass.put("boolean", boolean.class);
		privitiveClass.put("char", char.class);
		privitiveClass.put("byte", byte.class);
		privitiveClass.put("short", short.class);
		privitiveClass.put("int", int.class);
		privitiveClass.put("long", long.class);
		privitiveClass.put("float", float.class);
		privitiveClass.put("double", double.class);
		privitiveClass.put("void", void.class);
	}
	
	private static DynamicClassLoaderFactory dynamicClassLoaderFactory;
	private static DynamicClassLoaderFactory getDynamicClassLoaderFactory() {
		if (dynamicClassLoaderFactory == null) {
			dynamicClassLoaderFactory = ObjectFactory.getInstance().getBean(DynamicClassLoaderFactory.class, false);
		}
		return dynamicClassLoaderFactory;
	}
	
	public static boolean isCollection(Class<?> classType) {
		return classType != null && Collection.class.isAssignableFrom(classType);
	}
	
	public static boolean isSimpleObj(Class<?> classType) {
		boolean isSimpleObj = classType != null && (classType.isPrimitive() || classType.isEnum() 
			|| Number.class.isAssignableFrom(classType)
			|| String.class.equals(classType) || Date.class.isAssignableFrom(classType) 
			|| Boolean.class.equals(classType) || Byte.class.equals(classType));
		return isSimpleObj;
	}
	
	public static boolean isPrimitive(String className) {
		return privitiveClass.containsKey(className);
	}
	
	/**
	 * Get the class type according to the parameter information or action information
	 * 
	 * @return class name
	 */
	public static String getRuntimeType(String genericTypeName) {
		String typeName = null;
		 if (genericTypeName == GenericType.PARAM_MODEL) {
			ActionContext context = ActionContext.getContext();
			Object type = context.getParameters().get(FrameConstant.PARAM_GENERIC_TYPE);
			
			if (type != null && type instanceof String[]) {
				String[] types = (String[]) type;
				if (types.length > 0) {
					typeName = types[0];
					
					SafeManager safeManager = SafeManager.getInstance();
					if ("Map".equals(typeName)) {
						typeName = ParameterMap.class.getName();
					} else if (typeName != null && typeName.length() > 0 && safeManager.isEnablePermissionCheck()) {
						ActionContext actionContext =  ActionContext.getContext();
				    	if (actionContext != null) {
				    		HttpServletRequest request = (HttpServletRequest) actionContext.get(StrutsStatics.HTTP_REQUEST);
				    		UserInfo userInfo = safeManager.getLoginUser(request);
				    		String simpleName = ObjectFactory.getInstance().getBean(BeanIntrospector.class, false)
				    				.getSimpleName(typeName);
				    		String path = '/' + Routing.invoker2PathNoExtension(simpleName, Invoker.DEFAULT_SEPARATOR, "search", true)
				    				+ ".html";					// TODO: use functions.getActionExtension
				    		boolean pass;
				    		if (userInfo != null) {
				    			// TODO: better way?
				    			pass = safeManager.checkPage(userInfo.getLoginId(), path);
				    		} else {
				    			pass = safeManager.isFreeResource(path, true);
				    		}
				    		if (!pass) {
				    			throw new ApplicationException("errors.recordPermission");
				    		}
				    	}
					}
				}
			}
			
			// Use GenericType.MODEL
			if (typeName == null || typeName.length() == 0) {
				String actionName = (String) context.getValueStack().getContext().get(FrameConstant.STACK_ACTIONNAME);
				if (actionName != null) {
					BeanIntrospector beanIntrospector = ObjectFactory.getInstance().getBean(BeanIntrospector.class, false);
					typeName = beanIntrospector.getModelName(beanIntrospector.getSimpleName(actionName));
				}
			}
		} else if (genericTypeName == GenericType.MODEL) {
			ActionContext context = ActionContext.getContext();
			if (context != null) {
				String actionName = (String) context.getValueStack().getContext().get(FrameConstant.STACK_ACTIONNAME);
				if (actionName == null) {
					typeName = Object.class.getName();
				} else {
					BeanIntrospector beanIntrospector = ObjectFactory.getInstance().getBean(BeanIntrospector.class, false);
					typeName = beanIntrospector.getModelName(beanIntrospector.getSimpleName(actionName));
				}
			} else {
				typeName = Object.class.getName();
			}
			// TODO: cache the typeName?
		} else {
			typeName = genericTypeName;
		}
		
		return typeName;
	}
	
	public static Method getServiceMethod(String serviceName, String methodName) {
		Method found = null;
		try {
			Class clazz = Thread.currentThread().getContextClassLoader().loadClass(serviceName);
		
			Method[] methods = clazz.getMethods();
			for (Method m : methods) {
				if (m.getAnnotation(Protected.class) == null) {
					if (methodName.equals(m.getName())) {
						found = m;
						break;
					}
				}
			}
		} catch (ClassNotFoundException e) {
			if (logger.isDebugEnabled()) {
				logger.debug("Failed to find service method:" + serviceName + '.' + methodName);
			}
		}
		return found;
	}
	
	public static Class getClass(Object object) {
		if (object == null) {
			return null;
		}
		
		Class clazz = object.getClass();
		String className = clazz.getName();
		if (className.indexOf("ByCGLIB$$") > 0) {	// Enhancer.isEnhanced(clazz)
			return clazz.getSuperclass();
		} else if (object instanceof HibernateProxy) {
			try {
				object = HibernateUtil.unproxy(object);
				return object.getClass();
			} catch (LazyInitializationException ex) {
				logger.error("Can't get the detail class, object is detached instance" +
						" from another session: " + object);
				return object.getClass().getSuperclass();
			}
		} else {
			return clazz;
		}
	}
	
	public static Class getClass(String className) {
		return forName(className);
	}
	
	public static String getClassName(Object object) {
		String className = null;
		if (object instanceof Map) {
			if (object instanceof ParameterMap) {
				className = ((ParameterMap) object).getModelName();
				
				if (className == null) {
					className = getRuntimeType(GenericType.MODEL);
				}
			} else {
				className = (String)((Map)object).get(IDynamicModel.ENTITY_NAME);
			}
		}
		
		if (className == null) {
			className = getClass(object).getName();
			if (ModelInfo.isDynamicClass(className)) {
				className = ModelInfo.getStaticClassName(className);
			}
		}
		return className;
	}
	
	public static String getSimpleClassName(Object object) {
		String simpleClassName = null;
		if (object instanceof Map) {
			String className = (String)((Map)object).get(IDynamicModel.ENTITY_NAME);
			if (className != null) {
				int index = className.lastIndexOf('.');
				if (index > 0) {
					simpleClassName = className.substring(index + 1);
				}
			}
		}
		
		if (simpleClassName == null) {
			simpleClassName = getClass(object).getSimpleName();
		}
		return simpleClassName;
	}
	
	public static Class<?> forName(String className) {
		try {
			Class clazz = privitiveClass.get(className);
			if (clazz == null) {
				clazz = Class.forName(className, true, Thread.currentThread().getContextClassLoader());
			}
			return clazz;
		} catch (ClassNotFoundException ex) {
			BeanIntrospector beanIntrospector = BeanIntrospector.getInstance();
			Class clazz = null;
			if (beanIntrospector != null && (beanIntrospector.isModel(className) >= 0
					|| beanIntrospector.isService(className) >= 0)) {
				clazz = getDynamicClassLoaderFactory().forName(className);
				if (clazz == null && logger.isDebugEnabled()) {
					logger.debug("type is not valid static class:" + className);
				}				
			}
			return clazz;
		}
	}
	
	public static InputStream getResourceAsStream(String name, Class clazz) {
		return Thread.currentThread().getContextClassLoader().getResourceAsStream(name);			// struts: ClassLoaderUtils.getResourceAsStream
	}
	
	public static URL getResource(String name, Class clazz) {
		return Thread.currentThread().getContextClassLoader().getResource(name);			// struts: ClassLoaderUtils.getResource
	}

	public static Class<?> loadClass(String beanName, Class clazz) throws ClassNotFoundException {
		return Thread.currentThread().getContextClassLoader().loadClass(beanName);		// struts: ClassLoaderUtils.getResource
	}
}